
#include "EggNinePCH.h"

#include "camera.h"
#include "shaderDefs.h"
#include "mymath.h"

namespace EggNine {

PerspectiveCamera::PerspectiveCamera(float nearPlane, float farPlane, float fov, float aspectRatio, float tripodHeight/* =0.f */)
    : m_near(nearPlane)
    , m_far(farPlane)
    , m_fov(fov)
    , m_ar(aspectRatio)
    , m_tripodHeight(tripodHeight)
{
    m_matrices.Projection(3,2)=-1;
    m_inverseMatrices.Projection(2,3)=-1;
}

void PerspectiveCamera::Reshape(int width, int height)
{
    m_ar=(float)width/height;

    UpdateProjection();
}

void PerspectiveCamera::UpdateProjection()
{
    float tmp=tanf(m_fov/2);
    mat4& invProj=m_inverseMatrices.Projection;
    invProj(0,0)=tmp;
    invProj(1,1)=tmp/m_ar;

    tmp=1.f/tmp;
    mat4& proj=m_matrices.Projection;
    proj(0,0)=tmp;
    proj(1,1)=tmp*m_ar;

#ifdef ZBUFFER
    proj(2,2)=-(m_far+m_near)/(m_far-m_near);
    proj(2,3)=2.f*m_far*m_near/(m_far-m_near);

    invProj(3,2)=(m_far-m_near)/(2*m_far*m_near);
    invProj(3,3)=-(m_far+m_near)/(2*m_far*m_near);
#else
    // WBUFFER
    proj(2,2)=2.f/(m_far-m_near);
    proj(2,3)=-(m_far+m_near)/(m_far-m_near);

    invProj(3,2)=-(m_far-m_near)/(m_far+m_near);
    invProj(3,3)=-2.f/(m_far+m_near);
#endif // ZBUFFER
}

void PerspectiveCamera::ZoomBy(float magnitude)
{
    m_fov*=magnitude;
    if(m_fov >= M_PI)
        m_fov=(float)M_PI*.99f;

    UpdateProjection();
}

void PerspectiveCamera::RotateAroundAxis(float x, float y, float z, float angle)
{
    // This is just a dummy. Doesn't fit with the camera model.
    assert(0);

#if 0
    // not tested for a long time

    // use Rodriguez's rotation formula
    mat4 kx;
    __m128 k;
    __m128i tmp, signs;

    k = _mm_setr_ps(x, y, z, 0);
    signs = _mm_cvtsi32_si128(1<<31);
    tmp = _mm_castps_si128(k);

    kx(1) = _mm_castsi128_ps(_mm_xor_si128(_mm_shuffle_epi32(tmp, _MM_SHUFFLE(3, 0, 3, 2)), signs));

    signs = _mm_slli_si128(signs, 4);
    kx(2) = _mm_castsi128_ps(_mm_xor_si128(_mm_shuffle_epi32(tmp, _MM_SHUFFLE(3, 3, 0, 1)), signs));

    signs = _mm_slli_si128(signs, 4);
    kx(0) = _mm_castsi128_ps(_mm_xor_si128(_mm_shuffle_epi32(tmp, _MM_SHUFFLE(3, 1, 2, 3)), signs));

    kx(3) = _mm_setzero_ps();

    kx*=sin(angle);

    mat4 kk;
    kk(0) = _mm_mul_ps(k, _mm_shuffle_ps(k, k, _MM_SHUFFLE(3,0,0,0)));
    kk(1) = _mm_mul_ps(k, _mm_shuffle_ps(k, k, _MM_SHUFFLE(3,1,1,1)));
    kk(2) = _mm_mul_ps(k, _mm_shuffle_ps(k, k, _MM_SHUFFLE(3,2,2,2)));
    kk-=mat4();
    kk*=1-cos(angle);
    kk+=mat4();

    kk+=kx;

    kk.MulRight(m_matrices.Camera)(3,3)=1.f; // This was reset by recent ops

    kk-=kx;
    kk-=kx;
    m_inverseMatrices.Camera*=kk;
    m_inverseMatrices.Camera(3,3)=1.f;
#elif 0
    // TODO: This can be drastically optimized by implementing SinCos function in SSE
    angle*=.5f;
    float tmp=sinf(angle);
    vec4 qR(x,y,z,0.f);
    qR.Normalize();
    qR*=tmp;
    qR+=vec4(0.f, 0.f, 0.f, cosf(angle));

    m_qBaseOrientation.QMulRight(qR);
    m_qBaseOrientation.Normalize();

#if 0
    // This is needed only if translation is applied after rotation
    m_Trans=qR.QConj(m_Trans);
#endif
#endif
}

void PerspectiveCamera::RotateCameraBy(float dphi, float dtheta, float dpsi)
{
    // Assumes small angles

    dphi*=.5f; dtheta*=.5f; dpsi*=.5f;
    // TODO: this can be expanded into a much simpler form. Maybe later...
    m_qRotPhi.QMulRight(vec4(0.f, dphi, 0.f, 1.f-dphi*dphi*.5f)).Normalize();
    m_qRotTheta.QMulRight(vec4(dtheta, 0.f, 0.f, 1.f-dtheta*dtheta*.5f)).Normalize();
    m_qRotPsi.QMulRight(vec4(0.f, 0.f, dpsi, 1.f-dpsi*dpsi*.5f)).Normalize();
}

void PerspectiveCamera::TranslateBy(float dx, float dy, float dz)
{
    // Either way we don't want that translation will be affected by psi rotation
    // In most cases it'll just look/feel strange
    // TODO: When psi rotation will be expanded, this needs to be expanded as well

#if 0
    vec4 trans=m_qRotPsi.QConj(vec4(dx,dy,dz,.0f));

    if(m_lockToY)
        m_Trans-=m_qRotTheta.QConj(trans);
    else
        m_Trans-=trans;
#endif

    vec4 trans=vec4(dx,dy,dz,0.f);
    if(!m_lockToY)
        trans=m_qRotTheta.QConj(trans);
    m_Trans+=m_qRotPhi.QConj(trans);
}

void PerspectiveCamera::TranslateTo(float x, float y, float z)
{
    m_Trans=m_qBaseOrientation.QBackConj(vec4(x,y,z,0.f));
}

void PerspectiveCamera::PointTo(const vec4& loc, const vec4& pt, const vec4& up)
{
    // TODO: Not tested

    // TODO: SSE4 can simplify here by blending, but currently we're bound by SSSE3
    m_Trans=loc.ShiftVectorElementsLeft<1>().ShiftVectorElementsRight<1>();

    vec4 pointVec=(loc*pt.Swizzle<3,3,3,3>()-pt);
    vec4 yVec=up;
    yVec.Normalize();
    vec4 xVec=yVec.Cross(pointVec).Normalize();
    vec4 zVec=xVec.Cross(yVec);
}

} // EggNine
